package com.fary.boot.autoconfigure.condition;

import com.fary.boot.autoconfigure.AutoConfigurationMetadata;
import com.fary.context.annotation.ConditionContext;
import com.fary.core.Ordered;
import com.fary.core.annotation.Order;
import com.fary.core.type.AnnotatedTypeMetadata;
import com.fary.util.ClassUtils;
import com.fary.util.ObjectUtils;

import com.fary.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;

import java.util.Map;

@Order(Ordered.HIGHEST_PRECEDENCE + 20)
class OnWebApplicationCondition extends FilteringSpringBootCondition {

    private static final String SERVLET_WEB_APPLICATION_CLASS = "org.springframework.web.context.support.GenericWebApplicationContext";

    private static final String REACTIVE_WEB_APPLICATION_CLASS = "org.springframework.web.reactive.HandlerResult";

    @Override
    protected ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
                                             AutoConfigurationMetadata autoConfigurationMetadata) {
        ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
        for (int i = 0; i < outcomes.length; i++) {
            String autoConfigurationClass = autoConfigurationClasses[i];
            if (autoConfigurationClass != null) {
                outcomes[i] = getOutcome(
                        autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnWebApplication"));
            }
        }
        return outcomes;
    }

    private ConditionOutcome getOutcome(String type) {
        if (type == null) {
            return null;
        }
        ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnWebApplication.class);
        if (ConditionalOnWebApplication.Type.SERVLET.name().equals(type)) {
            if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, getBeanClassLoader())) {
                return ConditionOutcome.noMatch(message.didNotFind("servlet web application classes").atAll());
            }
        }
        if (ConditionalOnWebApplication.Type.REACTIVE.name().equals(type)) {
            if (!ClassNameFilter.isPresent(REACTIVE_WEB_APPLICATION_CLASS, getBeanClassLoader())) {
                return ConditionOutcome.noMatch(message.didNotFind("reactive web application classes").atAll());
            }
        }
        if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, getBeanClassLoader())
                && !ClassUtils.isPresent(REACTIVE_WEB_APPLICATION_CLASS, getBeanClassLoader())) {
            return ConditionOutcome.noMatch(message.didNotFind("reactive or servlet web application classes").atAll());
        }
        return null;
    }

    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        boolean required = metadata.isAnnotated(ConditionalOnWebApplication.class.getName());
        ConditionOutcome outcome = isWebApplication(context, metadata, required);
        if (required && !outcome.isMatch()) {
            return ConditionOutcome.noMatch(outcome.getConditionMessage());
        }
        if (!required && outcome.isMatch()) {
            return ConditionOutcome.noMatch(outcome.getConditionMessage());
        }
        return ConditionOutcome.match(outcome.getConditionMessage());
    }

    private ConditionOutcome isWebApplication(ConditionContext context, AnnotatedTypeMetadata metadata,
                                              boolean required) {
        switch (deduceType(metadata)) {
            case SERVLET:
                return isServletWebApplication(context);
            case REACTIVE:
                return isReactiveWebApplication(context);
            default:
                return isAnyWebApplication(context, required);
        }
    }

    private ConditionOutcome isAnyWebApplication(ConditionContext context, boolean required) {
        ConditionMessage.Builder message = ConditionMessage.forCondition(ConditionalOnWebApplication.class,
                required ? "(required)" : "");
        ConditionOutcome servletOutcome = isServletWebApplication(context);
        if (servletOutcome.isMatch() && required) {
            return new ConditionOutcome(servletOutcome.isMatch(), message.because(servletOutcome.getMessage()));
        }
        ConditionOutcome reactiveOutcome = isReactiveWebApplication(context);
        if (reactiveOutcome.isMatch() && required) {
            return new ConditionOutcome(reactiveOutcome.isMatch(), message.because(reactiveOutcome.getMessage()));
        }
        return new ConditionOutcome(servletOutcome.isMatch() || reactiveOutcome.isMatch(),
                message.because(servletOutcome.getMessage()).append("and").append(reactiveOutcome.getMessage()));
    }

    private ConditionOutcome isServletWebApplication(ConditionContext context) {
        ConditionMessage.Builder message = ConditionMessage.forCondition("");
        if (!ClassNameFilter.isPresent(SERVLET_WEB_APPLICATION_CLASS, context.getClassLoader())) {
            return ConditionOutcome.noMatch(message.didNotFind("servlet web application classes").atAll());
        }
        if (context.getBeanFactory() != null) {
            String[] scopes = context.getBeanFactory().getRegisteredScopeNames();
            if (ObjectUtils.containsElement(scopes, "session")) {
                return ConditionOutcome.match(message.foundExactly("'session' scope"));
            }
        }
        if (context.getEnvironment() instanceof ConfigurableWebEnvironment) {
            return ConditionOutcome.match(message.foundExactly("ConfigurableWebEnvironment"));
        }
        if (context.getResourceLoader() instanceof WebApplicationContext) {
            return ConditionOutcome.match(message.foundExactly("WebApplicationContext"));
        }
        return ConditionOutcome.noMatch(message.because("not a servlet web application"));
    }

    private ConditionOutcome isReactiveWebApplication(ConditionContext context) {
        ConditionMessage.Builder message = ConditionMessage.forCondition("");
        if (!ClassNameFilter.isPresent(REACTIVE_WEB_APPLICATION_CLASS, context.getClassLoader())) {
            return ConditionOutcome.noMatch(message.didNotFind("reactive web application classes").atAll());
        }
        if (context.getEnvironment() instanceof ConfigurableReactiveWebEnvironment) {
            return ConditionOutcome.match(message.foundExactly("ConfigurableReactiveWebEnvironment"));
        }
        if (context.getResourceLoader() instanceof ReactiveWebApplicationContext) {
            return ConditionOutcome.match(message.foundExactly("ReactiveWebApplicationContext"));
        }
        return ConditionOutcome.noMatch(message.because("not a reactive web application"));
    }

    private Type deduceType(AnnotatedTypeMetadata metadata) {
        Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnWebApplication.class.getName());
        if (attributes != null) {
            return (Type) attributes.get("type");
        }
        return Type.ANY;
    }

}